/*
 * Copyright 2011 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.dctmcommons.impl;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dctmcommons.DctmCommonsException;
import org.dctmcommons.IAttribHelper;
import org.dctmcommons.IParamCheck;
import org.dctmcommons.IQueryHelper;
import org.dctmcommons.ITypedObjectCallback;

import com.documentum.com.DfClientX;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfQuery;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfTypedObject;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLogger;
import com.documentum.fc.common.IDfId;
import com.documentum.fc.common.IDfTime;
import com.documentum.fc.methodserver.DfStandardJobArgumentNames;

/**
 * Query-related helper classes.
 */
public class QueryHelper implements IQueryHelper {
	
	/** The parameter check instance. */
	private static final IParamCheck paramCheck = ParamCheck.getInstance();
	
	/** DQL to execute a server method. */
	private static final String DQL_RUN_METHOD_WITH_ARGS =
		"execute do_method with method = ''{0}'', arguments = ''{1}''";
	
    /**
     * Default constructor.
     */
    private QueryHelper() {}

    /**
     * Factory method.
     * @return The query helper instance.
     */
    public static IQueryHelper getInstance() {
        return new QueryHelper();
    }
    
    /*
     * (non-Javadoc)
     * @see org.dctmcommons.IQueryHelper#closeCollectionQuietly(com.documentum.fc.client.IDfCollection)
     */
    @Override
    public void closeCollectionQuietly(IDfCollection collection) {
    	try {
    		closeCollection(collection);
    	} catch (DfException e) {
    		DfLogger.debug(this, e.getLocalizedMessage(), null, e);
		}
    }
    
    /*
     * (non-Javadoc)
     * @see org.dctmcommons.IQueryHelper#closeCollection(com.documentum.fc.client.IDfCollection)
     */
    @Override
    public void closeCollection(IDfCollection collection) throws DfException {
    	if (collection != null) {
    		collection.close();
    	}
    }
    
    /*
     * (non-Javadoc)
     * @see org.dctmcommons.IQueryHelper#runMethod(com.documentum.fc.client.IDfSession, java.lang.String, java.util.Map)
     */
    @Override
    public void runMethod(final IDfSession session,
    					  final String methodName,
    					  final Map<String, String> methodArgs)
    	throws DfException {
    	
		paramCheck.checkObject(session, "session");
    	paramCheck.checkString(methodName, "methodName");
    	
    	final String dql = MessageFormat.format(
    										DQL_RUN_METHOD_WITH_ARGS,
    										methodName,
    										convertMethodArgsToDql(
    												methodArgs,
    												session));
    	
    	updateQuery(session, dql, IDfQuery.DF_EXEC_QUERY);
    }
    
    /**
     * Converts a {@link Map} of server method arguments to DQL. It is
     * assumed that argument keys are NOT prefixed with a dash and that
     * argument values are NOT delimited with double quotes.
     * @param args The method server arguments. Optional. Will automatically
     * add in current repository as an argument if necessary.
     * @param session The session
     * @return The DQL
     */
    private String convertMethodArgsToDql(final Map<String, String> args,
    									  final IDfSession session)
    	throws DfException {
    	
    	final String VALUE_TEMPLATE = "-{0} \"{1}\" ";
    	final String NO_VALUE_TEMPLATE = "-{0} ";
    	final StringBuilder sb = new StringBuilder();
    	
    	if (args != null) {
	    	final Set<String> keys = args.keySet();
	    	for (final String key : keys) {
	    		final String value = args.get(key);
	    		if (value != null && value.length() > 0) {
	    			sb.append(MessageFormat.format(VALUE_TEMPLATE, key, value));
	    		} else {
	    			sb.append(MessageFormat.format(NO_VALUE_TEMPLATE, key));
	    		}
	    	}
    	}
    	
    	if (sb.indexOf(DfStandardJobArgumentNames.DOCBASE_NAME) == -1) {
    		sb.append(MessageFormat.format(
    									VALUE_TEMPLATE,
    									DfStandardJobArgumentNames.DOCBASE_NAME,
    									session.getDocbaseName()));
    	}
    	
    	final String dql = sb.toString();
    	
    	if (DfLogger.isTraceEnabled(this)) {
    		DfLogger.trace(this,
    					   "Constructed DQL for method argments: {0}",
    					   new String[] { dql },
    					   null);
    	}
    	
    	return dql;
    }
    
    /*
     * (non-Javadoc)
     * @see org.dctmcommons.IQueryHelper#updateQuery(com.documentum.fc.client.IDfSession, java.lang.String, int)
     */
    @Override
	public void updateQuery(final IDfSession session,
    					    final String dql,
    					    final int queryType) throws DfException {
    	
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(dql, "dql");
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(IDfTypedObject typedObject) throws DfException {
				// do nothing
				return false;
			}
		});
    }
    
    /* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForCount(com.documentum.fc.client.IDfSession, java.lang.String)
	 */
    @Override
	public int queryForCount(final IDfSession session,
    						 final String qualification) throws DfException {
    	
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(qualification, "qualification");
		
		final String dql = MessageFormat.format("select count(*) from {0}",
												qualification);
		
		return queryForSingleIntValue(session, dql, IDfQuery.DF_READ_QUERY);
    }
    
    /*
     * (non-Javadoc)
     * @see org.dctmcommons.IQueryHelper#query(com.documentum.fc.client.IDfSession, java.lang.String, int)
     */
    @Override
    public IDfCollection query(final IDfSession session,
    						   final String dql,
    						   final int queryType)
    		throws DfException {

		paramCheck.checkObject(session, "session");
		paramCheck.checkString(dql, "dql");
		
		final IDfQuery query = new DfClientX().getQuery();
		query.setDQL(dql);
		
		return query.execute(session, queryType);
    }
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#query(com.documentum.fc.client.IDfSession, java.lang.String, int, org.dctmcommons.ITypedObjectCallback)
	 */
	@Override
	public void query(final IDfSession session,
					  final String dql,
					  final int queryType,
					  final ITypedObjectCallback callback)
		throws DfException {
		
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(dql, "dql");
		paramCheck.checkObject(callback, "callback");
		
		IDfCollection results = null;
		final IDfQuery query = new DfClientX().getQuery();
		
		query.setDQL(dql);
		
		try {
			results = query.execute(session, queryType);
			
			if (results != null) {
				while (results.next()) {
					if (!callback.read(results)) {
						break;
					}
				}
			}
		} catch (DfException e) {
			DfLogger.error(this, "Failed to perform query", null, e);
			throw e;
		} finally {
			closeCollection(results);
		}
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForListOfMaps(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public List<Map<String, Object>> queryForListOfMaps(
													final IDfSession session,
													final String dql,
													final int queryType)
		throws DfException {
		final MapBasedTypedObjectTransformer transformer =
								MapBasedTypedObjectTransformer.getInstance();
		query(session, dql, queryType, transformer);
		return transformer.getResults();
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleStringValues(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public String[] queryForSingleStringValues(final IDfSession session,
										       final String dql,
										       final int queryType)
		throws DfException {
		
		final IAttribHelper attribHelper = AttribHelper.getInstance();
		final List<String> results = new ArrayList<String>();
		final List<String> attribNames = new ArrayList<String>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException {
				if (attribNames.size() == 0) {
					final String[] keys = attribHelper.getAttribNames(
																typedObject);
					for (final String key : keys) {
						attribNames.add(key);
					}
				}
				
				for (final String attribName : attribNames) {
					results.add(typedObject.getString(attribName));
				}
				
				return true;
			}
		});
		
		if (results.size() == 0) {
			return null;
		} else {
			return results.toArray(new String[] {});
		}
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleStringValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public String queryForSingleStringValue(final IDfSession session,
									        final String dql,
									        final int queryType)
		throws DfException {
		final String[] attribValue = new String[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				attribValue[0] = typedObject.getValueAt(0).asString();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingStringValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public String[] queryForRepeatingStringValue(final IDfSession session,
												 final String dql,
												 final int queryType)
		throws DfException {
		final List<String> attribValues = new ArrayList<String>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					attribValues.add(typedObject
											.getRepeatingString(attribName, i));
				}
				
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new String[] {});
	}

	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleIntValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public int queryForSingleIntValue(final IDfSession session,
								      final String dql,
								      final int queryType)
		throws DfException {
		final int[] attribValue = new int[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				attribValue[0] = typedObject.getValueAt(0).asInteger();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingIntValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public Integer[] queryForRepeatingIntValue(final IDfSession session,
											   final String dql,
											   final int queryType)
		throws DfException {
		final List<Integer> attribValues = new ArrayList<Integer>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					attribValues.add(typedObject
											.getRepeatingInt(attribName, i));
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new Integer[] {});
	}

	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleBooleanValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public boolean queryForSingleBooleanValue(final IDfSession session,
								              final String dql,
								              final int queryType)
		throws DfException {
		final boolean[] attribValue = new boolean[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				attribValue[0] = typedObject.getValueAt(0).asBoolean();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingBooleanValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public Boolean[] queryForRepeatingBooleanValue(final IDfSession session,
											       final String dql,
											       final int queryType)
		throws DfException {
		final List<Boolean> attribValues = new ArrayList<Boolean>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					attribValues.add(typedObject
										.getRepeatingBoolean(attribName, i));
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new Boolean[] {});
	}

	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleDoubleValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public double queryForSingleDoubleValue(final IDfSession session,
								            final String dql,
								            final int queryType)
		throws DfException {
		final double[] attribValue = new double[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				attribValue[0] = typedObject.getValueAt(0).asDouble();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingDoubleValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public Double[] queryForRepeatingDoubleValue(final IDfSession session,
											     final String dql,
											     final int queryType)
		throws DfException {
		final List<Double> attribValues = new ArrayList<Double>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					attribValues.add(typedObject
										.getRepeatingDouble(attribName, i));
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new Double[] {});
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleIdValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public IDfId queryForSingleIdValue(final IDfSession session,
								       final String dql,
								       final int queryType)
		throws DfException {
		final IDfId[] attribValue = new IDfId[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				attribValue[0] = typedObject.getValueAt(0).asId();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingIdValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public IDfId[] queryForRepeatingIdValue(final IDfSession session,
											final String dql,
											final int queryType)
		throws DfException {
		final List<IDfId> attribValues = new ArrayList<IDfId>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					attribValues.add(typedObject
											.getRepeatingId(attribName, i));
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new IDfId[] {});
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleIdValueAsString(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public String queryForSingleIdValueAsString(final IDfSession session,
								                final String dql,
								                final int queryType)
		throws DfException {
		final String[] attribValue = new String[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				final IDfId id = typedObject.getValueAt(0).asId();
				attribValue[0] = (id == null) ? null : id.getId();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingIdValueAsString(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public String[] queryForRepeatingIdValueAsString(final IDfSession session,
											         final String dql,
											         final int queryType)
		throws DfException {
		final List<String> attribValues = new ArrayList<String>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					final IDfId id = typedObject.getRepeatingId(attribName, i);
					if (id != null) {
						attribValues.add(id.getId());
					}
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new String[] {});
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForSingleDateValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public Date queryForSingleDateValue(final IDfSession session,
								        final String dql,
								        final int queryType)
		throws DfException {
		final Date[] attribValue = new Date[1];
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				final IDfTime time = typedObject.getValueAt(0).asTime();
				attribValue[0] = (time == null) ? null : time.getDate();
				return false;
			}
		});
		
		return attribValue[0];
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.impl.IQueryHelper#queryForRepeatingDateValue(com.documentum.fc.client.IDfSession, java.lang.String, int)
	 */
	@Override
	public Date[] queryForRepeatingDateValue(final IDfSession session,
											 final String dql,
											 final int queryType)
		throws DfException {
		final List<Date> attribValues = new ArrayList<Date>();
		
		query(session, dql, queryType, new ITypedObjectCallback() {
			
			@Override
			public boolean read(final IDfTypedObject typedObject)
				throws DfException, DctmCommonsException {
				
				final String attribName = typedObject.getAttr(0).getName();
				final int numValues = typedObject.getValueCount(attribName);

				for (int i = 0; i < numValues; i++) {
					final IDfTime time = typedObject
											.getRepeatingTime(attribName, i);
					if (time != null) {
						attribValues.add(time.getDate());
					}
				}
				return false;
			}
		});
		
		return (attribValues.size() == 0) ?
					null :
						attribValues.toArray(new Date[] {});
	}

}
